'''
Created on Nov 20, 2011

@author: kllvql


This project takes an image and grayScales it in 6 tones. 
It then replaces each of those tones with a die of certain
colors to generate a diceScale image. The final image is 
saved according to certain naming conventions. Most of the
configuration is set with the variables in the DiceScaleConfig
file.
'''

from PIL import Image, ImageDraw
from DiceScaleConfig import *





def getPixel(image, x,y):
	'''
	Returns a specific pixel from an image. I'm not really sure why I need 
	this, I wrote it about a week ago when I was just starting Python. Now
	I would just do this in-line. 
	'''
	width, height = image.size
	pixels = image.getdata()
	pixel = pixels[x+y*width]
	return pixel

def drawPips(draw, pipXs, pipYs):
	'''
	Draws a pip at ever x,y combination as given by the arguments. pipXs
	is a list of xs and pipYs is a corresponding list of ys. draw is a 
	draw object generated by ImageDraw.Draw(image)
	'''
	for i in range(len(pipXs)):
		draw.ellipse([pipXs[i],pipYs[i],\
					  pipXs[i]+PIP_SIZE,pipYs[i]+PIP_SIZE], \
					 fill=DIE_FG)
	
	
def drawOne(image, x, y):
	'''
	Draws a one on the image at x,y.
	Returns a modified version of the image, though image given as an 
	argument is modified. 
	'''
	draw = ImageDraw.Draw(image)
	
	draw.rectangle([x,y,x+DIE_WIDTH,y+DIE_HEIGHT], fill=DIE_BG)
	
	pipXs = [x + DIE_WIDTH/2 - PIP_SIZE/2]
	pipYs = [y + DIE_HEIGHT/2 - PIP_SIZE/2]
	
	drawPips(draw,pipXs,pipYs)
	
	del draw
	return image


def drawTwo(image, x, y):
	'''
	Draws a two on the image at x,y.
	Returns a modified version of the image, though image given as an 
	argument is modified. 
	'''
	draw = ImageDraw.Draw(image)
	
	draw.rectangle([x,y,x+DIE_WIDTH,y+DIE_HEIGHT], fill=DIE_BG)
	
	pipXs = [x + DIE_WIDTH/3 - PIP_SIZE/2, \
			 x + 2*DIE_WIDTH/3 - PIP_SIZE/2]
	pipYs = [y + DIE_HEIGHT/3 - PIP_SIZE/2, \
			 y + 2*DIE_HEIGHT/3 - PIP_SIZE/2]
	
	drawPips(draw,pipXs,pipYs)
	
	del draw
	return image



def drawThree(image, x, y):
	'''
	Draws a three on the image at x,y.
	Returns a modified version of the image, though image given as an 
	argument is modified. 
	'''
	draw = ImageDraw.Draw(image)
	
	draw.rectangle([x,y,x+DIE_WIDTH,y+DIE_HEIGHT], fill=DIE_BG)
	
	pipXs = [x + DIE_WIDTH/3 - DIE_WIDTH%3 - PIP_SIZE/2, \
			 x + DIE_WIDTH/2 - PIP_SIZE/2, \
			 x + 2*DIE_WIDTH/3 + 2*DIE_WIDTH%3 - PIP_SIZE/2]
	pipYs = [y + DIE_HEIGHT/3 - DIE_WIDTH%3 - PIP_SIZE/2, \
			 y + DIE_HEIGHT/2 - PIP_SIZE/2, \
			 y + 2*DIE_HEIGHT/3 + 2*DIE_WIDTH%3 - PIP_SIZE/2]
	
	drawPips(draw,pipXs,pipYs)
	
	del draw
	return image


def drawFour(image, x, y):
	'''
	Draws a four on the image at x,y.
	Returns a modified version of the image, though image given as an 
	argument is modified. 
	'''
	draw = ImageDraw.Draw(image)
	
	draw.rectangle([x,y,x+DIE_WIDTH,y+DIE_HEIGHT], fill=DIE_BG)
	
	pipXs = [x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2]
	pipYs = [y + DIE_HEIGHT/4- PIP_SIZE/2, \
			 y + 3*DIE_HEIGHT/4 - PIP_SIZE/2, \
			 y + DIE_HEIGHT/4 - PIP_SIZE/2, \
			 y + 3*DIE_HEIGHT/4 - PIP_SIZE/2]
	
	drawPips(draw,pipXs,pipYs)
	
	del draw
	return image

def drawFive(image, x, y):
	'''
	Draws a five on the image at x,y.
	Returns a modified version of the image, though image given as an 
	argument is modified. 
	'''
	draw = ImageDraw.Draw(image)
	
	draw.rectangle([x,y,x+DIE_WIDTH,y+DIE_HEIGHT], fill=DIE_BG)
	
	pipXs = [x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + DIE_WIDTH/2 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2]
	pipYs = [y + DIE_HEIGHT/4- PIP_SIZE/2, \
			 y + 3*DIE_HEIGHT/4 - PIP_SIZE/2, \
			 y + DIE_HEIGHT/2 - PIP_SIZE/2, \
			 y + DIE_HEIGHT/4 - PIP_SIZE/2, \
			 y + 3*DIE_HEIGHT/4 - PIP_SIZE/2]
	
	drawPips(draw,pipXs,pipYs)
	
	del draw
	return image

def drawSix(image, x, y):
	'''
	Draws a six on the image at x,y.
	Returns a modified version of the image, though image given as an 
	argument is modified. 
	'''
	draw = ImageDraw.Draw(image)
	
	draw.rectangle([x,y,x+DIE_WIDTH,y+DIE_HEIGHT], fill=DIE_BG)
	
	pipXs = [x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2, \
			 x + 3*DIE_WIDTH/4 - PIP_SIZE/2]
	pipYs = [y + 3*DIE_HEIGHT/16 - PIP_SIZE/2, \
			 y + DIE_HEIGHT/2 - PIP_SIZE/2, \
			 y + 13*DIE_HEIGHT/16 - PIP_SIZE/2, \
			 y + 3*DIE_HEIGHT/16 - PIP_SIZE/2, \
			 y + DIE_HEIGHT/2 - PIP_SIZE/2, \
			 y + 13*DIE_HEIGHT/16 - PIP_SIZE/2]
	
	drawPips(draw,pipXs,pipYs)
	
	del draw
	return image

def averageValueToDieNumber(average):
	'''
	Accepts as input the average color intensity of a pixel (in the 
	range 0-255, and scales it to 0-6.0
	'''
	return average * 6.0 / 255.0


def grayscaleValue(image, xy):
	'''
	Finds the value for a given pixel range in image. The range is given
	by xy = [x1,y1,x2,y2]. This finds the average color intensity over 
	this range of pixels and outputs a corresponding value on the scale
	0.0-6.0. 
	'''
	sumVal = 0.0
	total = 0.0
	for x in range(xy[2] - xy[0]):
		for y in range(xy[3] - xy[1]):
			pixel = getPixel(image, xy[0] + x, xy[1] + y)
			for chanel in range(len(pixel)):\
				sumVal += pixel[chanel]
			total += len(pixel)
	
	averageValue = sumVal / total

	return averageValueToDieNumber(averageValue)
	
def drawDieFunction(original, scale, breakPoints, xy):
	'''
	This takes as input the original image, the scale for
	this particular iteration, the breakPoints for this
	iteration, and the xy values for the range that 
	this function will be applied to. The xy values are
	of form [x1,y1,x2,y2]. This outputs the drawDie 
	function appropriate for the given range on the 
	original image. I love treating functions as data.
	It reminds me of LISP :-)
	'''
	value = grayscaleValue(original, [xy[0],xy[1], \
									  xy[0]+16/scale, \
									  xy[1]+16/scale])
	if value > breakPoints[0]:
		return drawSix
	elif value > breakPoints[1]:
		return drawFive
	elif value > breakPoints[2]:
		return drawFour
	elif value > breakPoints[3]:
		return drawThree
	elif value > breakPoints[4]:
		return drawTwo
	else:
		return drawOne

# xy is of the form [x1,y1] on the original image
def drawDie(original, diceScale, scale, breakPoints, xy):
	'''
	This function accepts as input the original image, the image which
	will be the diceScale version, the scale value for this iteration, 
	the breakPoints for this iteration, and the xy values for the 
	range the die will be for. The xy values are of form [x1,y1,x2,y2].
	This function returns a modified version of diceScale.
	'''
	drawFn = drawDieFunction(original, scale, breakPoints, xy)
	return drawFn(diceScale, xy[0]*scale, xy[1]*scale)
	
def drawDice(original, diceScale, scale, breakPoints):
	'''
	This function accepts as input the original image, the image which
	will be the diceScale version, the scale for this iteration, and the
	breakPoints for this iteration. It returns a diceScaled version of 
	the image after iterating through the original image. 
	'''
	for x in range(original.size[0]*scale/DIE_WIDTH):
		for y in range(original.size[1]*scale/DIE_HEIGHT):
			diceScale = drawDie(original, diceScale, scale, breakPoints, \
								[x*DIE_WIDTH/scale, y*DIE_HEIGHT/scale])
	return diceScale


def diceScale():
	'''
	This function diceScales an image based off of the configuration
	information found in DiceScaleConfig. 
	'''

	'''
	dataFile is a file which contains various data about every generated
	image, such as the total number of dice, the height in dice, and the
	width in dice. Useful stuff. 
	'''
	dataFile = open(outputMark + fileName + "Data.txt", 'w')


	for k in range(len(fileNameVersions)):
		thisFileName = fileName + fileNameVersions[k]
		
		original = Image.open(thisFileName + fileExtension)
		
		thisFileName = outputMark + thisFileName
		
		dataFile.write("Information for " + thisFileName + fileExtension + "\n")
		
		
		'''
		Iterates through the list of BREAK_POINTS and SCALES to cover every
		combination. 
		'''
		for i in range(len(BREAK_POINTS)):
			dataFile.write("\n	 For break points at ")
			for j in range(len(BREAK_POINTS[i])):
				dataFile.write(str(BREAK_POINTS[i][j]))
			dataFile.write("\n")
			for j in range(len(SCALES)):
				'''
				I couldn't figure out how to break loops properly in Python,
				so I have this breakLoop variable. It is ugly, but it works
				'''
				loop = True
				for l in range(len(EXCLUSIONS)):
					exclusion = EXCLUSIONS[l]
					if exclusion[0] == k & exclusion[1] == j:
						loop = False
						print "Break loop at " + `exclusion[0]` + \
												 `exclusion[1]` + \
												 `loop`
						break
				if loop:
					print "Loop at " + `k` + ":" + `j`
					# New Image based off of the scale. So ugly. 
					diceScale = Image.new("RGBA", [original.size[0]*SCALES[j], \
												   original.size[1]*SCALES[j]])
					drawDice(original, diceScale, SCALES[j], BREAK_POINTS[i])
				
					# File name for the new output
					thisFile = thisFileName + SCALE_NAMES[j] + "ResDice" + `i` + \
							   fileExtension
					diceScale.save(thisFile)
					print "Done with " + thisFile
					
					# All of this is just info about the generated image
					diceRows = original.size[0]*SCALES[j]/DIE_WIDTH
					diceCols = original.size[1]*SCALES[j]/DIE_HEIGHT
					diceCount =  diceRows * diceCols
								   
					dataFile.write("		  " + thisFile + " dice count is " \
								   + `diceCount` + "\n")
					dataFile.write("		  with " + `diceRows` + " rows and " + \
								   `diceCols` + " collums.\n")
			dataFile.write("\n")
		dataFile.write("\n\n")
		
	dataFile.close()


